/**
 * Android FriendFeed Client
 * Copyright (C) 2010 E & B Labs
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.eblabs.friendfeed;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;

import java.util.ArrayList;
import net.eblabs.json.JsonClient;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;

import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * API de FriendFeed.
 * @author Ivan (epplestun@gmail.com)
 */
public class FriendFeedAPI extends JsonClient {

    /**
     * Instancia única de la API en toda la aplicación.
     */
    private static FriendFeedAPI FFAPI;
    /**
     * URI del timeline.
     */
    private static final String TIMELINE_URL =
            "http://friendfeed-api.com/v2/feed/";
    /**
     * URI para recuperar la imagen del usuario.
     */
    private static final String USER_PICTURE_URL =
            "http://friendfeed-api.com/v2/picture/";
    /**
     * URI para añadir un comentario a una entrada.
     */
    private static final String ADD_COMMENT_URL =
            "http://friendfeed-api.com/v2/comment";
    /**
     * URI para añadir una nueva entrada.
     */
    private static final String ADD_ENTRY_URL =
            "http://friendfeed-api.com/v2/entry";
    /**
     * URI para indicar si gusta o no una entrada.
     */
    private static final String LIKE_UNLIKE_COMMENT_URL =
            "http://friendfeed-api.com/v2/";
    /**
     * URI para recuperar la lista de amigos del usaurio.
     */
    private static final String FRIENDS_URL =
            "http://friendfeed-api.com/v2/feedinfo/home";
    /**
     * URI para ocultar una entrada.
     */
    private static final String HIDE_URL =
            "http://friendfeed-api.com/v2/hide";
    /**
     * HOST de FriendFeed.
     */
    private static final String FRIENDFEED_HOST = "friendfeed-api.com";

    /**
     * Constructor de la clase.
     */
    private FriendFeedAPI() {
        super(FRIENDFEED_HOST);
    }

    /**
     * Constructor sobrecargado.
     * @param username Nombre de usuario
     * @param remoteKey remoteKey del usuario
     */
    private FriendFeedAPI(final String username, final String remoteKey) {
        this();
        this.setCredentials(username, remoteKey);
    }

    /**
     * Devuelve la instancia de la clase.
     * @return FriendFeedAPI
     */
    public static FriendFeedAPI getInstance() {
        if (FFAPI == null) {
            FFAPI = new FriendFeedAPI();
        }

        return FFAPI;
    }

    /**
     * Devuelve la instancia de la clase sobrecargado.
     * @param username Nombre de usuario
     * @param remoteKey remoteKey del usuario
     * @return FriendFeedAPI
     */
    public static FriendFeedAPI getInstance(final String username,
            final String remoteKey) {
        getInstance().setCredentials(username, remoteKey);

        return FFAPI;
    }

    /**
     * Comprueba la respuesta del servidor y que sea correcta.
     * @param response Respuesta a comprobar.
     * @return Respuesta comprobada.
     * @throws ApiException en caso de error controlada por FriendFeed.
     * @throws IOException en caso de error de lectura.
     * @throws JSONException en caso de error con la respuesta JSON.
     */
    public static HttpResponse checkResponseError(HttpResponse response)
            throws ApiException, IOException, JSONException {

        if(response.getStatusLine().getStatusCode() == 200) {
            return response;
        }

        JSONObject json = new JSONObject(
                stringifyStream(response.getEntity().getContent()));
        switch(response.getStatusLine().getStatusCode()) {
            case 401:
                throw new UnAuthorizedException();
            case 403:
                if(json.getString("errorCode")
                        .equals("unauthorized")) {
                    throw new UnAuthorizedException();
                } else if(json.getString("errorCode")
                        .equals("forbidden")) {
                    throw new ForbiddenException();
                } else if(json.getString("errorCode")
                        .equals("limit-exceeded")) {
                    throw new LimitExceededException();
                } else if(json.getString("errorCode")
                        .equals("feed-not-found")) {
                    throw new FeedNotFountException();
                } else if(json.getString("errorCode")
                        .endsWith("-required")) {
                    String arg = json.getString("errorCode").split("-")[0];
                    throw new ArgException(arg);

                } else {
                    throw new ApiException(403, json.getString("error"));
                }
            default:
                throw new ApiException(response.getStatusLine().getStatusCode(),
                        json.getString("error"));
        }
    }

    private InputStream requestData(HttpUriRequest httpRequest)
            throws ApiException {
        Log.d(FriendFeedAPI.class.getName(),
                "Sending " + httpRequest.getMethod()
                + " request to " + httpRequest.getURI().toString());

        InputStream result;

        try {
            HttpResponse response = checkResponseError(
                    this.getResponse(httpRequest));

            result = response.getEntity().getContent();
        } catch (ApiException ex) {
            throw ex;
        } catch (Exception e) {
            ApiException ex = new ApiException(-1,
                    "Unexpected Error: " + e.toString());
            ex.initCause(e);
            throw ex;
        }

        return result;
    }

    private final JSONObject requestJSONData(HttpUriRequest httpRequest)
            throws ApiException, IOException, JSONException {

        InputStream data = requestData(httpRequest);

        JSONObject json = null;

        json = new JSONObject(stringifyStream(data));
        data.close();

        return json;
    }

    public JSONObject getUserTimeline()
            throws ApiException, IOException, JSONException {
        Log.d(FriendFeedAPI.class.getName(), "Requesting user timeline.");

        String URI = TIMELINE_URL + this.getUsername() + "?pretty=1";

        HttpGet httpGet = new HttpGet(URI);

        return requestJSONData(httpGet);
    }

    public JSONObject getFriendsTimeline()
            throws ApiException, IOException, JSONException {
        Log.d(FriendFeedAPI.class.getName(), "Requesting friends timeline.");

        String URI = TIMELINE_URL + this.getUsername() + "/friends?pretty=1";

        HttpGet httpGet = new HttpGet(URI);

        return requestJSONData(httpGet);
    }

    public JSONObject addComment(String comment, String entry) throws
            ApiException, IOException, JSONException, URISyntaxException {

        ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("body", comment));
        params.add(new BasicNameValuePair("entry", entry));

        URI uri = new URI(ADD_COMMENT_URL);

        HttpPost post = new HttpPost(uri);
        post.getParams().setBooleanParameter("http.protocol.expect-continue",
                false);
        post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

        InputStream data = requestData(post);

        JSONObject json = null;
        json = new JSONObject(stringifyStream(data));

        return json;
    }

    public JSONObject addNewEntry(String entry, String to,
            ArrayList<File> files) throws
            ApiException, IOException, JSONException, URISyntaxException {

        URI uri = new URI(ADD_ENTRY_URL);

        HttpPost post = new HttpPost(uri);
        post.getParams().setBooleanParameter("http.protocol.expect-continue",
                false);

        MultipartEntity multipartContent = new MultipartEntity();

        if (to.length() > 0) {
            multipartContent.addPart("to", new StringBody(to));
        }

        multipartContent.addPart("body", new StringBody(entry));

        for (int i = 0; i < files.size(); i++) {
            File file = new File(files.get(i).getAbsolutePath());

            multipartContent.addPart("file[]", new FileBody(file));

            /*
            InputStream is = new FileInputStream(file);
            byte[] imageBytes = IOUtils.toByteArray(is);

            InputStreamKnownSizeBody isb = new InputStreamKnownSizeBody(
                new ByteArrayInputStream(imageBytes), imageBytes.length,
                "image/png", files.get(i).getName());

            multipartContent.addPart("file", isb);

            //HttpMultipartMode.
            */
        }

        post.setEntity(multipartContent);

        InputStream data = requestData(post);

        JSONObject json = null;
        json = new JSONObject(stringifyStream(data));
        
        return json;
    }

    public JSONObject likeOrUnlikeEntry(String entry, String type)
            throws URISyntaxException,
            UnsupportedEncodingException, ApiException, IOException,
            JSONException {
        ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("entry", entry));

        String URI = LIKE_UNLIKE_COMMENT_URL + type;

        URI uri = new URI(URI);

        HttpPost post = new HttpPost(uri);
        post.getParams().setBooleanParameter("http.protocol.expect-continue",
                false);
        post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

        InputStream data = requestData(post);
        data.close();

        JSONObject json = null;
        json = new JSONObject(stringifyStream(data));

        return json;
    }

    public JSONObject hideEntry(String entry) throws
            URISyntaxException, UnsupportedEncodingException, ApiException,
            IOException, JSONException {

        Log.d("entryid", entry);

        ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("entry", entry));

        String URI = HIDE_URL;

        URI uri = new URI(URI);

        HttpPost post = new HttpPost(uri);
        post.getParams().setBooleanParameter("http.protocol.expect-continue",
                false);
        post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

        InputStream data = requestData(post);
        data.close();

        JSONObject json = null;
        json = new JSONObject(stringifyStream(data));

        return json;
    }

    public Bitmap getUserPicture(String username, String size) throws
            IOException, UnAuthorizedException, ApiException {
        String URL = USER_PICTURE_URL + username + "?size=" + size;

        HttpGet httpGet = new HttpGet(URL);

        InputStream pictureStream = requestData(httpGet);

        Bitmap bitmap = null;

        bitmap = BitmapFactory.decodeStream(pictureStream);
        pictureStream.close();

        return bitmap;
    }

    public JSONObject getFriends()
            throws ApiException, IOException, JSONException {
        Log.d(FriendFeedAPI.class.getName(), "Requesting user friends.");

        String URI = FRIENDS_URL+ "?pretty=1";

        HttpGet httpGet = new HttpGet(URI);

        return requestJSONData(httpGet);
    }
}
